package com.congee02.dbservlet.framework.ioc.container;

import com.congee02.dbservlet.framework.ioc.annotation.*;
import com.congee02.dbservlet.framework.ioc.bean.BeanDefinition;
import com.congee02.dbservlet.framework.ioc.bean.ScopeEnum;
import com.congee02.dbservlet.framework.ioc.servlet.MappingHttpServlet;
import com.congee02.dbservlet.framework.ioc.utils.FileUtils;

import javax.servlet.ServletContextEvent;
import javax.servlet.http.HttpServlet;
import java.beans.Introspector;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * 简单的基于注解的 IoC 容器
 * 支持按名查找，按类查找
 * 会另外将 有 WebServlet 注解的 HttpServlet 添加到一个列表让Tomcat注册
 * {@link com.congee02.dbservlet.listener.IoCContainerContextListener#contextInitialized(ServletContextEvent)}
 */
public class SimpleAnnotationApplicationContext {

    /**
     * bean 定义 map
     */
    private final ConcurrentMap<String, BeanDefinition> beanDefinitionMap;

    /**
     * 单例模式对象直接创建
     */
    private final ConcurrentMap<String, Object> singletonObjects;
    /**
     * 单例模式按类查找支持
     */
    private final ConcurrentMap<Class<?>, Object> singletonClazzObject;

    private final CopyOnWriteArrayList<MappingHttpServlet> httpServlets;

    public SimpleAnnotationApplicationContext(Class<?> configClazz) {

        beanDefinitionMap = new ConcurrentHashMap<>();
        singletonObjects = new ConcurrentHashMap<>();
        singletonClazzObject = new ConcurrentHashMap<>();
        httpServlets = new CopyOnWriteArrayList<>();

        // 判断配置类有无 ComponentScan 注解
        ComponentScan componentScan = configClazz.getAnnotation(ComponentScan.class);
        if (componentScan == null) {
            throw new NullPointerException("配置类需要 ComponentScan 注解来指明要扫描的包范围");
        }

        String[] packageNames = componentScan.scanPackages();
        ClassLoader classLoader = getClass().getClassLoader();
        // 扫描所有包下的类
        for (String packageName : packageNames) {
            scanPackage(packageName, classLoader);
        }

    }

    private void scanPackage(String basePackageName, ClassLoader classLoader) {

        ConcurrentMap<String, BeanDefinition> temp = new ConcurrentHashMap<>();

        // 将包名转为路径
        String packagePath = basePackageName.replace(".", "/");
        URL packageResource = classLoader.getResource(packagePath);


        if (packageResource == null) {
            throw new IllegalStateException("包 " + basePackageName + " 不存在");
        }
        File classDir = new File(packageResource.getFile());
        if (classDir.isFile()) {
            throw new IllegalStateException("包 " + basePackageName + " 不存在");
        }
        // 向下遍历所有字节码文件
        String classDirAbsolutePath = classDir.getPath() + File.separator;
        ArrayList<File> classFiles = new ArrayList<>();
        FileUtils.searchFiles(classDir, classFiles);
        if (classFiles.isEmpty()) {
            return;
        }
        try {
            for (File classFile : classFiles) {
                String classFileName = classFile.getName();
                if (! classFileName.endsWith(".class")) {
                    continue;
                }
                String currentClassAbsolutePath = classFile.getAbsolutePath();
                String currentClassCanonicalPath = currentClassAbsolutePath.substring(classDirAbsolutePath.length());
                String fullyQualifiedName = basePackageName + "." + currentClassCanonicalPath.substring(0, currentClassCanonicalPath.indexOf(".class"));
                fullyQualifiedName = fullyQualifiedName.replace("/", ".").replace("\\", ".");
                Class<?> beanClazz = Class.forName(fullyQualifiedName);
                // 跳过注解
                if (beanClazz.isAnnotation()) {
                    continue;
                }
                Component component = beanClazz.getAnnotation(Component.class);

                if (component == null) {
                    continue;
                }

                // bean 名称默认为类名第一个字母小写
                String beanName = component.value();
                beanName = "".equals(beanName) ? Introspector.decapitalize(beanClazz.getSimpleName()) : beanName;

                // 查看是否设置 Scope，默认为 SINGLETON
                Scope scope = beanClazz.getAnnotation(Scope.class);
                ScopeEnum scopeValue = scope == null ? ScopeEnum.SINGLETON : scope.value();

                // 若为 WebServlet，则另外装入一个列表中，供 Tomcat 动态注册
                if (beanClazz.isAnnotationPresent(WebServlet.class)) {
                    if (beanClazz.isAnnotationPresent(Scope.class)) {
                        throw new IllegalStateException("所有 Servlet 必须是单例的，仅允许使用单例");
                    }
                    if (! HttpServlet.class.isAssignableFrom(beanClazz)) {
                        throw new IllegalStateException("WebServlet 注解的类必须是 HttpServlet 类或其子类");
                    }
                }

                BeanDefinition definition = new BeanDefinition(beanClazz, scopeValue);
                temp.put(beanName, definition);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        beanDefinitionMap.putAll(temp);

        // 单例bean直接创建对象到 singletonObjects 和 singletonClazzObject
        for (String beanName : temp.keySet()) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            if (ScopeEnum.SINGLETON.equals(beanDefinition.getScope())) {
                Object bean = createBean(beanName, beanDefinition);
                Class<?> type = beanDefinition.getType();
                WebServlet webServlet = type.getAnnotation(WebServlet.class);
                if (webServlet != null) {
                    httpServlets.add(new MappingHttpServlet(beanName, (HttpServlet) bean, webServlet.mappings()));
                }
                singletonObjects.put(beanName, bean);
                singletonClazzObject.put(type, bean);
            }
        }
    }


    /**
     * 创建 bean 并配置依赖
     * @param beanName  bean 名称
     * @param definition    bean 定义
     * @return  创建的 bean 实例
     */
    private Object createBean(String beanName, BeanDefinition definition) {
        Class<?> beanType = definition.getType();
        try {
            Object instance = beanType.getDeclaredConstructor().newInstance();
            Field[] fields = beanType.getDeclaredFields();
            for (Field field : fields) {

                if (field.isAnnotationPresent(Autowired.class)) {
                    field.setAccessible(true);
                    field.set(instance, getBean(field.getName()));
                }
            }
            return instance;
        } catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 按名获取 bean
     */
    public Object getBean(String beanName) {
        BeanDefinition definition = beanDefinitionMap.get(beanName);
        if (definition == null) {
            throw new NullPointerException(beanName);
        }
        ScopeEnum scope = definition.getScope();
        if (ScopeEnum.SINGLETON.equals(scope)) {
            Object bean = singletonObjects.get(beanName);
            if (bean == null) {
                bean = createBean(beanName, definition);
                singletonObjects.put(beanName, bean);
            }
            return bean;
        }
        return createBean(beanName, definition);
    }

    /**
     * 按类获取 bean
     * @param clazz 字节码对象
     */
    @SuppressWarnings("unchecked")
    public <T> T getBean(Class<T> clazz) {
        return (T) singletonClazzObject.get(clazz);
    }

    /**
     * 获取所有的 HttpServlet
     */
    public List<MappingHttpServlet> getHttpServlets() {
        return httpServlets;
    }

    public void shutDown() {
        beanDefinitionMap.clear();
        singletonClazzObject.clear();
        singletonObjects.clear();
        httpServlets.clear();
    }
}
